import { Canvas, PPM } from './Canvas';
import { PartBase } from '../model/parts/PartBase';
import { StudFigure } from './StudFigure';
import { WireFigure } from './WireFigure';
import { FigureBase } from './FigureBase';
import { Stud } from '../model/Stud';


export const SnapStepPart = 4

export class PartFigure extends FigureBase {

    protected name?: raphaeljs.Element

    protected allElements: raphaeljs.Set
    protected draggableElements: raphaeljs.Set

    protected growSet: raphaeljs.Set
    protected glowOutline?: raphaeljs.Element
    protected glow?: raphaeljs.Set

    protected detailTextSet: raphaeljs.Set

    protected box?: raphaeljs.Element

    public wires: WireFigure[] = []

    public studFigures: StudFigure[] = []

    public width = 0
    public height = 0

    constructor(public canvas: Canvas, public part: PartBase) {

        super('part', part)

        if(!this.width) {
            this.width =  ((this.part.json.style?.width) || this.part.studs[0].length * 8) * PPM
        }
        if(!this.height) {
            this.height =  ((this.part.json.style?.height) || this.part.studs.length * 8) * PPM
        }

        this.allElements = canvas.paper.set()
        this.draggableElements = canvas.paper.set()

        this.growSet = canvas.paper.set()
        this.detailTextSet = canvas.paper.set()

        this.createChild()

        this.initDragable()
        
        this.on('active', ()=>{
            this.actived = true
            this.glow && this.glow.show()
            this.updateGlowSet()
        })
        this.on('unactive', ()=>{
            this.glow && this.glow.hide()
            this.actived = false
        })

        this.canvas.on('zoom', ()=> this.setDetailVisibale(this.detailTextSet) )
    }

    public startDragging(domX:number, domY:number, callback: ()=>void) {
        let [cx, cy] = this.positionDocumentToCanvas(domX,domY)
        
        let box = this.allElements.getBBox()

        this.moveTo(
            cx-box.width/2 + this.canvas.paper.viewbox.x, 
            cy-box.height/2 + this.canvas.paper.viewbox.y
        )
        
        // 模拟 touchstart 事件
        let evt = document.createEvent("MouseEvents")
        let dEles: any = this.draggableElements
        evt.initMouseEvent("touchstart", true, true, window, 0, 0, 0, domX, domY, false, false, false, false, 0, null)
        if(dEles.items && dEles.items[0] && dEles.items[0].node)
            dEles.items[0].node.dispatchEvent(evt)

        if(callback)
            this.once('drop', callback)
    }

    public positionDocumentToCanvas(docX:number, docY:number, bSnap=false) {

        docX/= this.canvas.paper.viewbox.scale
        docY/= this.canvas.paper.viewbox.scale
        
        if(bSnap) {
            [docX, docY] = this.snap(docX, docY)
        }
        return [docX, docY]
    }

    public snap(x:number, y:number) {
        let snap = 4 * PPM
        x = Math.round(x/snap) * snap
        y = Math.round(y/snap) * snap
        return [x,y]
    }

    private initDragable() {

        if(this.allElements.length<1)
            return

        let dragging = false
        let dragAnchor = (this.allElements as any)[0]
        let dragStartAt = {x:0 , y:0}
        let detal = { x:0, y:0 }

        this.draggableElements.drag(
            // dragging
            (dx: number, dy: number)=>{

                this.selector.dragging(dx, dy)
    
                if(!dragging)
                    return
    
                [dx, dy] = this.positionDocumentToCanvas(dx, dy, true)
    
                let bbox = dragAnchor.getBBox()
    
                detal.x = dx - (bbox.x - dragStartAt.x)
                detal.y = dy - (bbox.y - dragStartAt.y)
    
                if(detal.x==0 && detal.y==0){
                    return
                }
    
                this.move( detal.x, detal.y )
    
                // 移动发光体
                if(this.actived) {
                    this.updateGlowSet()
                }
                // 移动连线
                for(let wire of this.wires) {
                    wire.update()
                }
                
            } ,

            // start drag
            (x: number, y: number, e)=>{

                if(!this.onDragging(x, y, e)) {
                    dragging = false
                    return
                }
                dragging = true
                let bbox = dragAnchor.getBBox()
                let [sx, sy] = this.snap(bbox.x, bbox.y)

                this.move(sx-bbox.x, sy-bbox.y)

                dragStartAt.x = sx
                dragStartAt.y = sy
            } ,

            // end drag
            ()=>{
                dragging = true
                this.emit("drop")
            } ,
        )
    }

    protected onDragging(x: number, y: number, e: any) {
        return true
    }

    /**
     * 创建 子元素对象
     * 
     * @param elements part的所有子元素，随 part 一起移动
     * @param draggableElements 可响应拖拽的部分
     */
    protected createChild() {
        
        // 选中后的外发光
        this.createGlow()
        if(this.glowOutline) {
            this.allElements.push(this.glowOutline)
        }

        // 主体
        this.createBox()

        // 颗粒
        let posx = 0
        let posy = 0
        for(let r=0; r<this.part.studs.length; r++) {
            for(let c=0; c<this.part.studs[r].length; c++) {
                let stud = this.part.studs[r][c]
                this.createPort(stud, (posx+c*8)*PPM, (posy+r*8)*PPM)
            }
        }

        // 名称
        this.name = this.canvas.paper.text(0.5*PPM, -1.8*PPM, this.title())
                            .attr({
                                'font-size': 2*PPM ,
                                'text-anchor': "start"
                            })
        this.allElements.push(this.name as raphaeljs.Element)

        this.part.on("changed:instanceName", ()=>{
            this.name?.attr('text', this.title())
        })
    }

    protected title() {
        return this.part.instanceName || this.part.title || this.part.name
    }

    
    protected createBox() {
        let style: any = {}

        if(this.part.json && this.part.json.style && this.part.json.style.borderColor) {
            style["stroke-width"] = 1
            style["stroke"] = this.part.json.style.borderColor
        }
        else {
            style["stroke-width"] = 0
        }


        if( this.part.json && this.part.json.style && this.part.json.style.color ){
            style.fill = this.part.json.style.color
        }
        else{
            style.fill = "#bbb"
        }
        
        this.box = this.canvas.paper.rect(0, 0, this.width, this.height)
                    .attr(style)

        this.selector.setup(this.box as raphaeljs.Element)
        this.draggableElements.push(this.box as raphaeljs.Element)
        this.allElements.push(this.box as raphaeljs.Element)
    }

    protected createPort(stud: Stud, x:number, y:number) {

        stud.figure = new StudFigure(this, stud)
        stud.figure.set.translate(x, y)
        this.studFigures.push(stud.figure)

        this.growSet.push(stud.figure.set)

        this.allElements.push(stud.figure.port as raphaeljs.Element)
        this.allElements.push(stud.figure.name as raphaeljs.Element)

        this.detailTextSet.push(stud.figure.name as raphaeljs.Element)
        this.setDetailVisibale(stud.figure.name as raphaeljs.Element)

        return stud.figure
    }

    protected createGlow() {
        this.glowOutline = this.canvas.paper.rect(0, 0, this.width, this.height, 1)
                            .attr({
                                "stroke": "#757700" ,
                                "stroke-opacity": 0.1
                            })
        this.glow = (this.glowOutline as raphaeljs.Element).glow({
            'width': 16, 'fill': false, 'opacity': 0.5, 'color': '#f3f700'
        })
        this.glow.hide()

        this.growSet.insertAfter(this.glow)
    }

    private updateGlowSet() {
        if(!this.glow) {
            return
        }
        let {x,y} = (this.glowOutline as raphaeljs.Element).getBBox()
        this.glow.transform(`T${x},${y}`)
    }

    public remove(){
        this.glowOutline && this.glowOutline.remove()
        ;(this.glowOutline as any) = null

        this.growSet && this.growSet.remove()
        ;(this.growSet as any) = null

        this.glow && this.glow.remove()
        ;(this.glow as any) = null

        this.name && this.name.remove()
        ;(this.name as any) = null

        this.studFigures.forEach((s:StudFigure)=>{
            s.remove()
        })
        this.studFigures.splice(0)

        this.wires.forEach(w=>{
            w.remove()
        })
        this.wires.splice(0)

        this.allElements.remove()
        ;(this.allElements as any) = null

        ;(this.draggableElements as any).remove()
        ;(this.draggableElements as any) = null
        
        if(this.part && this.part.figure==this) {
            this.part.figure
            ; (this.part as any) = null
        }
    }

    public addWire(wire: WireFigure) {
        if(!this.wires.includes(wire))
            this.wires.push(wire)
    }
    public removeWire(wire: WireFigure) {
        this.wires.removeAll(wire)
    }

    public setDetailVisibale(el: raphaeljs.Element) {
        if(this.canvas.paper.viewbox.scale>=1.5) {
            this.detailTextSet.show()
            this.detailTextSet.attr("opacity", 0.2+(this.canvas.paper.viewbox.scale - 1.5)/1.5)
        } else
            this.detailTextSet.hide()
    }

    public bbox() {
        return this.allElements.getBBox()
    }

    public move(dx:number, dy:number) {
        this.allElements.translate(dx, dy)
    }
    public moveTo(x:number, y:number) {
        let bbox = this.bbox()
        this.move(
            x - bbox.x ,
            y - bbox.y
        )
    }

    public serialize() {
        let bbox = this.bbox()
        return {
            pos: { x:bbox.x, y:bbox.y }
        }
    }
    public unserialize(json: any) {
        this.moveTo(json.pos.x, json.pos.y)
    }
}